// License: http://www.codeplex.com/entlibcontrib/Project/License.aspx
// Author: Randy

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Globalization;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.Logging.Configuration;
using EntLibContrib.Logging.Configuration;

namespace EntLibContrib.Logging.Parser
{
    /// <summary>
    /// Contains functions for reading log files and log setting from configuration files.
    /// </summary>
    public class CategoryLogFileReader : LogEntryParser
    {
        #region Constructor
        /// <summary>
        /// Constructor to initialize a new instance of the class.
        /// </summary>
        public CategoryLogFileReader()
            : base()
        {
        }
        /// <summary>
        /// Constructor to initialize a new instance of the class.
        /// </summary>
        /// <param name="theTimeStampParser">The time stamp parser to use</param>
        public CategoryLogFileReader(ITimeStampParser theTimeStampParser)
            : base(theTimeStampParser)
        {
        }
        #endregion

        #region IEnumerable Catagory Names
        /// <summary>
        /// Iterates through each catagory name in the app configuration logging settings.
        /// </summary>
        /// <returns>an enumerator that iterates through each category name</returns>
        public static IEnumerable<string> AppCatagoryNames()
        {
            IConfigurationSource configurationSource = ConfigurationSourceFactory.Create();
            LoggingSettings loggingSettings = CategoryLogFileReader.GetLoggingSettings(configurationSource);
            foreach (string categoryName in LogCatagoryNames(loggingSettings))
            {
                yield return categoryName;
            }
        }

        /// <summary>
        /// Iterates through each catagory name in the logging configuration settings.
        /// </summary>
        /// <param name="configurationFilepath">configuration file with the logging settings</param>
        /// <returns>an enumerator that iterates through each category name</returns>
        public static IEnumerable<string> ConfigCatagoryNames(string configurationFilepath)
        {
            LoggingSettings loggingSettings = GetLoggingSettings(configurationFilepath);
            foreach (TraceSourceData tsd in loggingSettings.TraceSources)
            {
                yield return tsd.Name;
            }
        }

        /// <summary>
        /// Iterates through each catagory name in the logging configuration settings.
        /// </summary>
        /// <param name="loggingSettings">logging configuration settings</param>
        /// <returns>an enumerator that iterates through each category name</returns>
        private static IEnumerable<string> LogCatagoryNames(LoggingSettings loggingSettings)
        {
            foreach (TraceSourceData tsd in loggingSettings.TraceSources)
            {
                yield return tsd.Name;
            }
        }

        #region
        #endregion
        #endregion

        #region GetConfigTraceListenerFileName

        /// <summary>
        /// Get the log file name from the specified category name in the logging settings configuration file.
        /// </summary>
        /// <param name="configurationFilepath">configuration file with the logging settings</param>
        /// <param name="categoryName">category name</param>
        /// <returns>name of the log file</returns>
        public static string GetConfigTraceListenerFileName(string configurationFilepath, string categoryName)
        {
            LoggingSettings loggingSettings = GetLoggingSettings(configurationFilepath);
            TraceSourceData categoryTraceSourceData = GetConfigTraceSourceData(loggingSettings, categoryName);
            return GetFileTraceListener(loggingSettings, categoryTraceSourceData).FileName;
        }

        /// <summary>
        /// Get the log file name from the specified category name in the app settings configuration file.
        /// </summary>
        /// <param name="categoryName">category name</param>
        /// <returns>name of the log file</returns>
        public static string GetConfigTraceListenerFileName(string categoryName)
        {
            IConfigurationSource configurationSource = ConfigurationSourceFactory.Create();
            LoggingSettings loggingSettings = CategoryLogFileReader.GetLoggingSettings(configurationSource);
            TraceSourceData categoryTraceSourceData = GetConfigTraceSourceData(loggingSettings, categoryName);
            return GetFileTraceListener(loggingSettings, categoryTraceSourceData).FileName;
        }

        #endregion

        #region GetLoggingSettings

        /// <summary>
        /// Get the logging settings from the specified configuration file.
        /// </summary>
        /// <param name="configurationFilepath">configuration file with the logging settings</param>
        /// <returns>the logging settings</returns>
        public static LoggingSettings GetLoggingSettings(string configurationFilepath)
        {
            return GetLoggingSettings(new FileConfigurationSource(configurationFilepath));
        }
        /// <summary>
        /// Get the logging settings from the specified configuration source.
        /// </summary>
        /// <param name="configurationSource">logging configuration source</param>
        /// <returns>the logging settings</returns>
        public static LoggingSettings GetLoggingSettings(IConfigurationSource configurationSource)
        {
            LoggingSettings loggingSettings =
                configurationSource.GetSection(LoggingSettings.SectionName)
                as LoggingSettings;
            if (loggingSettings == null)
                throw new LoggingSectionNotDefinedException();
            return loggingSettings;
        }

        #endregion

        #region GetConfigTraceSourceData
        /// <summary>
        /// Get the configuration source data from the specified category name in the logging settings.
        /// </summary>
        /// <param name="loggingSettings">logging configuration settings</param>
        /// <param name="categoryName">category name</param>
        /// <returns>configuration source data for the category</returns>
        public static TraceSourceData GetConfigTraceSourceData(LoggingSettings loggingSettings, string categoryName)
        {
            TraceSourceData categoryTraceSourceData = loggingSettings.TraceSources.Get(categoryName);
            if (categoryTraceSourceData == null)
                throw new LoggingCategoryNotDefinedException();
            return categoryTraceSourceData;
        }
        #endregion

        #region GetFileTraceListener
        /// <summary>
        /// Iteration for each of the trace listeners in the category.
        /// </summary>
        /// <param name="loggingSettings">logging configuration settings</param>
        /// <param name="categoryTraceSourceData">configuration source data</param>
        /// <returns>each of the trace listeners</returns>
        public static IEnumerable<TraceListenerData> GetTraceListeners(LoggingSettings loggingSettings, TraceSourceData categoryTraceSourceData)
        {
            // Loop through all trace listeners in the logging settings
            foreach (TraceListenerData logTraceListener in loggingSettings.TraceListeners)
            {
                // Loop through all trace listeners named for this category
                foreach (TraceListenerReferenceData categoryTraceListener in categoryTraceSourceData.TraceListeners)
                {
                    // Use the category's trace listener name to identify the corresponding trace listener
                    if (categoryTraceListener.Name == logTraceListener.Name)
                    {
                        yield return logTraceListener;
                    }
                }
            }
        }
        /// <summary>
        /// Get the trace listener that implements the file properties.
        /// </summary>
        /// <param name="loggingSettings">logging configuration settings</param>
        /// <param name="categoryTraceSourceData">configuration source data</param>
        /// <returns>the trace listener that implements the file properties</returns>
        /// <remarks>
        /// Categories can reference multiple trace listeners, so 
        /// find the best match based on similarity between the category name and trace listener name
        /// </remarks>
        public static IFileTraceListenerData GetFileTraceListener(LoggingSettings loggingSettings, TraceSourceData categoryTraceSourceData)
        {
            IFileTraceListenerData exactMatch = null;
            IFileTraceListenerData alikeMatch = null;
            IFileTraceListenerData otherMatch = null;
            // Loop through all trace listeners in the category
            foreach (TraceListenerData logTraceListener in GetTraceListeners(loggingSettings, categoryTraceSourceData))
            {
                // Only want trace listeners that have file properties i.e. FileName, Footer, Formatter, Header, Name
                IFileTraceListenerData fileData = FileTraceListenerData.GetFileData(logTraceListener);
                if (fileData != null)
                {
                    if (categoryTraceSourceData.Name == logTraceListener.Name)
                    {
                        exactMatch = fileData;
                    }
                    else if (categoryTraceSourceData.Name.Contains(logTraceListener.Name) ||
                        logTraceListener.Name.Contains(categoryTraceSourceData.Name))
                    {
                        alikeMatch = fileData;
                    }
                    else
                    {
                        otherMatch = fileData;
                    }
                }
            }
            if (exactMatch != null)
                return exactMatch;
            if (alikeMatch != null)
                return alikeMatch;
            if (otherMatch != null)
                return otherMatch;
            throw new LoggingFileTraceListenerNotFoundException();
        }
        #endregion

        #region ConcatCompleteTemplate
        /// <summary>
        /// Concatenate the complete template text with header and footer.
        /// </summary>
        /// <param name="loggingSettings">logging configuration settings</param>
        /// <param name="fileTraceListenerData">logging file trace listener configuration data with the common properties</param>
        /// <returns>the complete template text with header and footer</returns>
        public static string ConcatCompleteTemplate(LoggingSettings loggingSettings, IFileTraceListenerData fileTraceListenerData)
        {
            ITemplateTextFormatterData textFormatter = GetTextFormatterData(loggingSettings, fileTraceListenerData);
            return RegularExpressionBuilder.ConcatCompleteTemplate(
                fileTraceListenerData.Header,
                textFormatter.Template,
                fileTraceListenerData.Footer);
        }
        #endregion

        #region GetTextFormatterData
        /// <summary>
        /// Get the logging text formatter configuration data for the logging trace listener.
        /// </summary>
        /// <param name="loggingSettings">logging configuration settings</param>
        /// <param name="fileTraceListenerData">logging file trace listener configuration data with the common properties</param>
        /// <returns>logging text formatter configuration data</returns>
        public static ITemplateTextFormatterData GetTextFormatterData(LoggingSettings loggingSettings, IFileTraceListenerData fileTraceListenerData)
        {
            if (fileTraceListenerData.Formatter == null || fileTraceListenerData.Formatter == "")
                throw new LoggingFormatterNotDefinedException();
            FormatterData theFormatter = loggingSettings.Formatters.Get(fileTraceListenerData.Formatter);
            ITemplateTextFormatterData templateFormatter = GetTextFormatterData(theFormatter);
            if (templateFormatter == null)
                throw new LoggingTextFormatterNotFoundException();
            return templateFormatter;
        }
        /// <summary>
        /// Get the logging template text formatter configuration data.
        /// </summary>
        /// <param name="theFormatter">configuration settings for a log formatter</param>
        /// <returns>logging text formatter configuration data</returns>
        public static ITemplateTextFormatterData GetTextFormatterData(FormatterData theFormatter)
        {
            ITemplateTextFormatterData templateFormatter = theFormatter as ITemplateTextFormatterData;
            if (templateFormatter == null)
            {
                TextFormatterData textFormatter = theFormatter as TextFormatterData;
                if (textFormatter != null)
                {
                    templateFormatter = new ExposeTextFormatterData(textFormatter);
                }
            }
            return templateFormatter;
        }
        /// <summary>
        /// Expose the properties of the <see cref="TextFormatterData"/>.
        /// </summary>
        private class ExposeTextFormatterData : ITemplateTextFormatterData
        {
            private TextFormatterData myData;

            /// <summary>
            /// Constructor to initialize a new instance of the class.
            /// </summary>
            public ExposeTextFormatterData(TextFormatterData theData)
            {
                myData = theData;
            }

            /// <summary>
            /// Name of the trace listener instance.
            /// </summary>
            public string Name
            {
                get { return myData.Name; }
            }

            /// <summary>
            /// Template containing tokens to replace by the text formatter.
            /// </summary>
            public string Template
            {
                get { return myData.Template; }
            }
        }
        #endregion

        #region IEnumerable Parse Log

        /// <summary>
        /// Iterates through each log entry using the specified trace listener in configuration file to build the template.
        /// </summary>
        /// <param name="configurationFilepath">configuration file with the logging settings</param>
        /// <param name="categoryName">category name</param>
        /// <param name="logFileName">name of the log file to parse, or null to use the one specified in the configuration</param>
        /// <param name="filter">null for all or the filter range of interest</param>
        /// <returns>an enumerator that iterates through each log entry in the filter range</returns>
        public IEnumerable<LogEntry> ParseLogFile(string configurationFilepath, string categoryName, string logFileName, Filter filter)
        {
            LoggingSettings loggingSettings = GetLoggingSettings(configurationFilepath);
            TraceSourceData categoryTraceSourceData = GetConfigTraceSourceData(loggingSettings, categoryName);
            IFileTraceListenerData fileTraceListenerData = GetFileTraceListener(loggingSettings, categoryTraceSourceData);
            if (logFileName == null)
                logFileName = fileTraceListenerData.FileName;
            string completeTemplate = ConcatCompleteTemplate(loggingSettings, fileTraceListenerData);
            string logFileText = FileIO.ReadAllText(logFileName);
            foreach (LogEntry ent in ParseLogText(completeTemplate, logFileText, filter))
            {
                yield return ent;
            }
        }

        /// <summary>
        /// Iterates through each log entry that matches the template.
        /// </summary>
        /// <param name="template">log entry template for creating the regular expression</param>
        /// <param name="logFileText">log file text</param>
        /// <param name="filter">null for all or the filter range of interest</param>
        /// <returns>an enumerator that iterates through each log entry in the filter range</returns>
        public IEnumerable<LogEntry> ParseLogText(string template, string logFileText, Filter filter)
        {
            Regex rex = RegularExpressionBuilder.DoBuildRegularExpressionFromTemplate(template);
            foreach (Match m in rex.Matches(logFileText))
            {
                LogEntry ent = DeseralizeLogEntry(m);
                if (filter != null)
                {
                    if (filter.Match(ent))
                    {
                        yield return ent;
                    }
                }
                else
                {
                    yield return ent;
                }
            }
        }

        #endregion
    }
}
